#!/usr/bin/python

"""
EffeLogger - Logger for your effectiveness
Copyright 2009 Mikhail Murzin

Version: 0.2

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License 
version 2 as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
"""

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys

AppName = QString('EffeLogger')
ConfigDir = QDir.homePath() + '/.config/' + QString(AppName).toLower()

def monthToFloat(month, year) :
	return year + month / 100.


#
# ColorButton
# 
# Class that "upgrades" a QPushButton to color button
#

class ColorButton (QObject) :
	def __init__ (self, button, color) :
		self.btn = button
		self.color = color
	
		button.setMaximumHeight(30)
		button.setMinimumHeight(30)
		button.setMaximumWidth(100)
		self.connect(self.btn, SIGNAL("clicked()"), self.clicked)
		self.setBtnColor(color);


	def clicked(self) :
		color = QColorDialog.getColor(self.color);
		if color.isValid() :
			self.setBtnColor(color)

	def setBtnColor(self, color) :
		self.color = color

		self.pm = QPixmap(self.btn.width() - 14, self.btn.height() - 14);
		p = QPainter(self.pm);
		# Draw a rectangle of selected color with light grey border
		p.setBrush(color);
		p.setPen(QPen(QColor(200, 200, 200)));
		p.drawRect(0, 0, self.pm.width(), self.pm.height());
		# Draw dark grey angle to make kind of a relief
		p.setPen(QPen(QColor(50, 50, 50)));
		p.drawLine(0, 0, self.pm.width(), 0);
		p.drawLine(0, 0, 0, self.pm.height());
		
		self.btn.setIconSize(self.pm.size());
		self.btn.setIcon(QIcon(self.pm));



#
# Settings
# 
# Class that stores application's settings
#

class Settings :
	def __init__ (self) :
		self.read()

	def read(self) :
		sett = QSettings(ConfigDir + "/" + AppName.toLower() + ".ini", QSettings.IniFormat)
		self.mainWindowGeometry = sett.value("mainWindowGeometry", QVariant(QRect(50, 50, 750, 450))).toRect()
		self.mainColor = QColor(sett.value("mainColor", QVariant(QColor(50, 50, 100))).toString())
		self.bgColor = QColor(sett.value("bgColor", QVariant(QColor(230, 230, 230))).toString())

	def write(self) :
		sett = QSettings(ConfigDir + "/" + AppName.toLower() + ".ini", QSettings.IniFormat)
		sett.setValue("mainWindowGeometry", QVariant(self.mainWindowGeometry))
		sett.setValue("mainColor", QVariant(self.mainColor.name()))
		sett.setValue("bgColor", QVariant(self.bgColor.name()))

settings = Settings()


#
# SettingsDlg
# 
# Settings dialog
#

class SettingsDlg (QDialog) :
	def __init__ (self, parent) :
		QDialog.__init__ (self, parent)
		self.setWindowTitle("Settings")
		
		# main color
		self.hBox1 = QHBoxLayout()
		self.mcLbl = QLabel("Main color:")
		self.mcBtn = QPushButton("")
		self.mcColorBtn = ColorButton(self.mcBtn, settings.mainColor)
		self.hBox1.addWidget(self.mcLbl)
		self.hBox1.addWidget(self.mcBtn)
		
		# background color
		self.hBox2 = QHBoxLayout()
		self.bgLbl = QLabel("Background color:")
		self.bgBtn = QPushButton("")
		self.bgColorBtn = ColorButton(self.bgBtn, settings.bgColor)
		self.hBox2.addWidget(self.bgLbl)
		self.hBox2.addWidget(self.bgBtn)
		
		self.closeBtn = QPushButton("Close")
		self.closeBtn.setDefault(True)
		self.closeBtn.setAutoDefault(True)
		self.connect(self.closeBtn, SIGNAL("clicked()"), self.accept)
		
		self.vBox = QVBoxLayout()
		self.setLayout(self.vBox)
		
		self.vBox.addLayout(self.hBox1)
		self.vBox.addLayout(self.hBox2)
		self.vBox.addWidget(self.closeBtn)


#
# CommentDlg
# 
# Dialog for comments editing
#

class CommentDlg (QDialog) :
	def __init__ (self, title, parent) :
		QDialog.__init__(self, parent)
		self.setWindowTitle(title)
		self.vBox = QVBoxLayout()
		self.edit = QTextEdit()
		self.closeBtn = QPushButton("Close")
		self.vBox.addWidget(self.edit)
		self.vBox.addWidget(self.closeBtn)
		self.setLayout(self.vBox)

		self.connect(self.closeBtn, SIGNAL("clicked()"), self.accept)

	def setComment(self, comment) :
		self.edit.setPlainText(comment)
		
	def getComment(self) :
		return self.edit.toPlainText()
		


#
# Data
# 
# Class that stores application data: day marks and comments
#

class Data :
	def __init__ (self):
		self.__data = dict()
		self.__comments = dict()
		self.__read()

	def __del__ (self) :
		print 'a'

	def __read (self) :
		file = QFile(ConfigDir + '/data')
		if file.open(QIODevice.ReadOnly) :
			while not file.atEnd() :
				line = QString.fromUtf8(file.readLine().data()).simplified()
				list = line.split('|')
				if list.count() < 2 :
					continue
				date = QDate.fromString(list.takeFirst(), "dd.MM.yyyy")
				mark = list.takeFirst().toFloat()[0]
				self.__data[date.toJulianDay()] = mark
				if not list.isEmpty() :
					comment = list.join("\n")
					if comment.length() > 0 :
						self.__comments[date.toJulianDay()] = comment
			file.close()

	def __save (self) :
		file = QFile(ConfigDir + '/data')
		if file.open(QIODevice.WriteOnly) :
			for index, mark in self.__data.items() :
				str = QDate.fromJulianDay(index).toString("dd.MM.yyyy") + "|" \
						+ QString.number(round(mark, 2))
				if index in self.__comments.keys() :
					str = str + "|" + self.__comments[index].split("\n").join("|")
				str = str + "\n"
				file.write(str.toUtf8())
			file.close()

	def firstMonth(self) :
		if len(self.__data) > 0 :
			keys = self.__data.keys()
			keys.sort()
			date = QDate.fromJulianDay(keys[0])
			return date.month(), date.year()
		else :
			date = QDate.currentDate()
			return date.month(), date.year()

	def dayHasComment(self, day, month, year) :
		index = QDate(year, month, day).toJulianDay()
		return ( index in self.__comments ) and ( self.__comments[index].length() > 0 )

	def dayMark(self, day, month, year) :
		index = QDate(year, month, day).toJulianDay()
		if index in self.__data :
			return self.__data[index]
		else :
			return -1
	
	def dayComment(self, jDay) :
		if jDay in self.__comments :
			return self.__comments[jDay]
		else :
			return QString("")

	def setDayMark(self, jDay, mark) :
		self.__data[jDay] = mark
		self.__save()

	def setDayComment(self, jDay, comment) :
		self.__comments[jDay] = comment
		self.__save()


#
# LoggerArea
# 
# Main widget for displaying data
#

class LoggerArea (QWidget) :
	def __init__ (self) :
		QWidget.__init__(self)
		
		self.data = Data()

		# parameters of painting
		self.mLeft = 0
		self.mRight = 0
		self.mTop = 0
		self.mBottom = 0
		self.dateWidth = 100
		self.cellSize = 20
		self.yearSpacing = 0
		
		# colors
		self.cellBorderColor = QColor(120, 120, 120)

		# limits
		self.firstMonth, self.firstYear = self.data.firstMonth()
		self.lastYear = self.firstYear + 1
		self.lastMonth = 12
		
		months = (self.lastYear - self.firstYear)*12 + (self.lastMonth - self.firstMonth) + 1
		
		self.fixedHeight = self.mTop + self.mBottom + months * self.cellSize + (self.lastYear - self.firstYear)*self.yearSpacing
		self.fixedWidth = self.mLeft + self.mRight + self.dateWidth + 31 * self.cellSize

		self.resize(self.fixedWidth, 450)
		self.setMinimumWidth(self.fixedWidth)
		self.setMinimumHeight(self.fixedHeight)
		
		#	cell menu
		self.dayMenu = QMenu()
		self.dayMenu.addAction("0%", self.markDayWith)
		self.dayMenu.addAction("50%", self.markDayWith)
		self.dayMenu.addAction("100%", self.markDayWith)
		self.dayMenu.addSeparator()
		self.dayMenu.addAction("Comment", self.dayComment)
		
		self.curDay = -1

	def startingMonth(self) :
		return self.firstMonth, self.firstYear

	def dayColor(self, day, month, year) :
		mark = self.data.dayMark(day, month, year)
		if mark < 0 :
			return settings.bgColor
		else :
			r = settings.mainColor.red()
			g = settings.mainColor.green()
			b = settings.mainColor.blue()
			return QColor( 255 - mark*(255 - r), 255 - mark*(255 - g), 255 - mark*(255 - b) )

	def markDayWith(self) :
		text = self.sender().text()
		text.chop(1)	# removing '%' symbol
		mark = text.toInt()[0] / 100.
		self.data.setDayMark(self.curDay, mark)
		self.curDay = -1
		self.update()

	def dayComment(self) :
		comment = self.data.dayComment(self.curDay)
		commentDlg = CommentDlg(QDate.fromJulianDay(self.curDay).toString("dd.MM.yyyy"), self)
		commentDlg.setComment(comment)
		if commentDlg.exec_() == QDialog.Accepted :
			self.data.setDayComment(self.curDay, commentDlg.getComment())
			self.update()

	def mouseReleaseEvent(self, event) :
		x = event.x()
		y = event.y()
		dMonths = (y - self.mTop) / self.cellSize
		if dMonths < 0 :
			return
		dYears = dMonths / 12
		year = self.firstYear + dYears
		month = self.firstMonth + (dMonths - dYears*12)
		if ( month > 12 ) :
			month = month - 12
			year = year + 1
		day = (x - self.mLeft - self.dateWidth) / self.cellSize + 1
		date = QDate(year, month, 1)
		if day <= 0 or day > date.daysInMonth() :
			return

		date = date.addDays(day - 1)

		print day, month, year, "-", date.toJulianDay()

		# popup menu
		self.curDay = date.toJulianDay()
		self.dayMenu.popup(event.globalPos())

	def paintEvent(self, event) :
		p = QPainter(self)
		self.paint(p)

	def paint(self, painter) :
		painter.setBrush(settings.bgColor)
		w = self.width()
		h = self.height()
		ww = w - self.mLeft - self.mRight
		hh = h - self.mTop - self.mBottom

		painter.setPen(settings.bgColor)
		painter.setBrush(settings.bgColor)
		painter.drawRect(0, 0, w, h)
		
		# draw content
		month, year = self.startingMonth()
		curDate = monthToFloat(month, year)
		lastDate = monthToFloat(self.lastMonth, self.lastYear)
		y = self.mTop

		curD = QDate.currentDate()
		cD, cM, cY = curD.day(), curD.month(), curD.year()
		
		painter.drawLine(self.mLeft + self.dateWidth, self.mTop, self.mLeft + self.dateWidth, self.mTop + hh)
		while curDate <= lastDate :
			# draw month
			painter.setPen(QColor(0, 0, 0))
			date = QDate(year, month, 1)
			str = date.toString("MMM yyyy")
			painter.drawText(self.mLeft, y, self.dateWidth - 2, self.cellSize, Qt.AlignRight | Qt.AlignVCenter, str)
			
			# draw cells
			days = date.daysInMonth()
			spacing = 2
			for day in range(0, days) :
				color = self.dayColor(day + 1, month, year)
				painter.setBrush(color)
				if year == cY and month == cM and day + 1 == cD :
					painter.setPen(QColor(220, 0, 0))
				else :
					painter.setPen(self.cellBorderColor)
				x = self.mLeft + self.dateWidth + day * self.cellSize
				painter.drawRect(x + spacing, y + spacing, self.cellSize - 2 * spacing, self.cellSize - 2 * spacing)
				if self.data.dayHasComment(day + 1, month, year) :
					xc = x + self.cellSize - spacing
					yc = y + spacing
					painter.setBrush(self.cellBorderColor)
					painter.drawPolygon(QPoint(xc - 4, yc), QPoint(xc, yc + 4), QPoint(xc, yc))

			month += 1
			if month == 13 :
				month = 1
				year += 1
				y += self.yearSpacing
			y += self.cellSize
			curDate = monthToFloat(month, year)


#
# MW
# 
# Application's main window. Contains LoggerArea and header
#

class MW (QWidget) :
	def __init__ (self) :
		QWidget.__init__(self)
		self.setWindowTitle(AppName)
		
		self.scrollArea = QScrollArea()
		self.scrollArea.setWidgetResizable(True)
		self.logger = LoggerArea()
		self.scrollArea.setWidget(self.logger)
		
		self.header = Header(self)
		
		self.vBox = QVBoxLayout()
		self.vBox.setMargin(0)
		self.vBox.setSpacing(0)
		self.vBox.addWidget(self.header)
		self.vBox.addWidget(self.scrollArea)
		self.setLayout(self.vBox)

	def moveEvent(self, event) :
		settings.mainWindowGeometry = self.geometry()

	def resizeEvent(self, event) :
		settings.mainWindowGeometry = self.geometry()

	def closeEvent(self, event) :
		settings.write()
		event.accept()

	def settings(self) :
		settDlg = SettingsDlg(self)
		if settDlg.exec_() == QDialog.Accepted :
			settings.mainColor = settDlg.mcColorBtn.color
			settings.bgColor = settDlg.bgColorBtn.color
			self.logger.update()

	def export(self) :
		fName = QFileDialog.getSaveFileName(self, "", QDir.homePath(), "PNG (*.png)")
		if not fName.isEmpty() :
			firstMonth, firstYear = self.logger.firstMonth, self.logger.firstYear
			lastMonth, lastYear = self.logger.lastMonth, self.logger.lastYear
			months = (lastYear - firstYear)*12 + (lastMonth - firstMonth) + 1
			
			w = self.logger.fixedWidth + self.logger.cellSize
			h = self.logger.fixedHeight + self.header.height
			self.px = QPixmap(w, h)
			self.px.fill(settings.bgColor)
			
			p = QPainter(self.px)
			x = self.logger.dateWidth
			for d in range(1, 32) :
				p.drawText(x, 0, self.logger.cellSize, self.header.height, \
						Qt.AlignCenter | Qt.AlignBottom, QString.number(d))
				x += self.logger.cellSize
			p.translate(QPoint(0, self.header.height))
			self.logger.paint(p)
			self.px.save(fName, "PNG", 0)

#
# Header
# 
# Application's header. Displays day numbers and tool buttons
#

class Header (QWidget) :
	def __init__ (self, mw) :
		QWidget.__init__(self)

		self.dateWidth = 100
		self.height = 30
		self.cellSize = 20
		self.setMinimumHeight(self.height)
		
		# settings button
		self.settBtn = QPushButton("s", self)
		self.settBtn.setGeometry(0, 0, 20, 20)
		self.connect(self.settBtn, SIGNAL("clicked()"), mw.settings)

		# export button
		self.expBtn = QPushButton("x", self)
		self.expBtn.setGeometry(22, 0, 20, 20)
		self.connect(self.expBtn, SIGNAL("clicked()"), mw.export)

	def paintEvent(self, event) :
		p = QPainter(self)

		# draw days
		x = self.dateWidth + 2
		for d in range(1, 32) :
			p.drawText(x, 0, self.cellSize, self.height, \
					Qt.AlignCenter | Qt.AlignBottom, QString.number(d))
			x += self.cellSize


if __name__ == '__main__' :
	if not QDir(ConfigDir).exists() :
		QDir().mkpath(ConfigDir)

	app = QApplication(sys.argv)
	mw = MW()
	mw.setGeometry(settings.mainWindowGeometry)
	mw.show()
	app.exec_()
